#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<assert.h>
#include<string.h>
#include<ctype.h>
#include<errno.h>

#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR   0
#define INPUT_REDIR  1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define trimSpace(start) do{ while(isspace(*start)) start++;   }while(0)


char lineCommand[NUM];
char* myargv[OPT_NUM];//指针数组
int lastCode = 0;
int lastSig = 0;

int redirType = NONE_REDIR;//重定向类型
char* redirFile = NULL;//重定向目标文件

//eg: "ls -a -l > myfile.txt" -> "ls -a -l" "myfile.txt"
//将得到的字符串解析为两部分。顺序扫描/倒序扫描
void commandCheck(char* commands)
{
    assert(commands);
    char* start = commands;
    char* end = commands + strlen(commands);
    while(start < end)
    {
        if(*start == '>')
        {
            *start = '\0';
            start++;
            if(*start == '>') //追加重定向
            {
                //"ls -a >>  log.txt"
                redirType = APPEND_REDIR;
                start++;
            }
            else{
                //"ls -a >  log.txt"
                redirType = OUTPUT_REDIR;
            }
            trimSpace(start);
            redirFile = start;
            break;
        }
        else if(*start == '<') //输入重定向
        {
            //"ls -a <    log.txt"
            *start = '\0';
            start++;
            //跳过空格
            trimSpace(start);
            //填写重定向信息
            //
            redirType = INPUT_REDIR;
            redirFile = start;
            break;
        }
        else{
            start++;
        }
    }
}

int main()
{
    while(1)
     {
        redirType = NONE_REDIR;
        redirFile = NULL;
        errno = 0;

        //输出提示符
        printf("用户名@主机号 当前路径# ");
        fflush(stdout);
        //获取用户输入,输入的时候会输入\n
        char* s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);
        assert(s!=NULL);
        //清除最后一个\n
        lineCommand[strlen(lineCommand)-1]=0;
        
        //解析字符串
        //"ls -a -l > myfile" ->  命令："ls -a -l" 重定向目标文件："myfile"
        commandCheck(lineCommand);
        //printf("test : %s\n",lineCommand);
        //切割字符串
        //"ls -a -l" -> "ls" "-a" "-l"
        myargv[0] = strtok(lineCommand," ");
        int i  = 1;
        //给ls加颜色
        if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)
        {
            myargv[i++] = "--color=auto";
        }
        while(myargv[i++] = strtok(NULL," "));//如果没有子字符串要切割，strtok返回NULL，而恰好myargv[end]也一定要= NULL
        if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)
        {
            if(myargv[1] != NULL)
            {
                chdir(myargv[1]);
            }
            continue;
        }
        if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0],"echo") == 0)
        {
            if(strcmp(myargv[1],"$?") == 0)
            {
                printf("%d\n",lastCode);
            }
            else if(strcmp(myargv[1],"$?") != 0)
            {
                for(int i = 1; myargv[i]; i++)
                {
                     printf("%s ",myargv[i]);
                }
                printf("\n");
            }
            else
            {
                printf("%s\n",myargv[1]);
            }
            continue;//重新循环，不执行fork创建子进程
        }
        //测试切割是否成功，条件编译
#ifdef DEBUG 
        for(int i = 0; myargv[i]; i++)
        {
            printf("myargv[%d]: %s\n",i,myargv[i]);
        }
#endif
         //执行命令
        pid_t id = fork();
        assert(id != -1);
        if(id  == 0)
        {
            switch(redirType)
            {
                case NONE_REDIR:
                    //nothing to do
                    break;
                case INPUT_REDIR:
                    {
                        //打开输入重定向目标文件
                        int fd = open(redirFile,O_RDONLY);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        //输入重定向
                        dup2(fd,0);
                    }
                    break;
                case OUTPUT_REDIR:
                case APPEND_REDIR:
                    {
                        umask(0);
                        int flags = O_WRONLY|O_CREAT;
                        if(redirType == OUTPUT_REDIR) flags |= O_TRUNC;
                        else flags |= O_APPEND;
                        int fd = open(redirFile,flags,0666);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        //输入、追加重定向
                        dup2(fd,1);
                    }
                    break;
                default:
                    printf("bug?\n");
                    break;
            }
            execvp(myargv[0],myargv);
            exit(1);
        }
        int status = 0;
        pid_t ret =  waitpid(id,&status,0);
        assert(ret > 0);
        lastCode = ((status>>8) & 0xFF);
        lastSig = (status & 0x7F);

    }
}
